avatar

目录
330 - cpp 默认全局命名空间与作用域运算符

C++ 默认全局命名空间与作用域运算符

🧑‍💻 User

In this lecture, we’re going to learn about the default global namespace, and this is nothing otherthan what you have outside any namespace.For example, here we have a piece of code.We have a function called Add.We have a namespace called my and inside the namespace we have another Add function and we can haveanother function called Do something.The add function we have on the outside is living in the global namespace because it’s not living inany namespace and anything outside any namespace is said to live in the default global namespace.So this is the idea here.Once we have the setup like we do here, we can go in the main function and try to use the functionswe have here.For example, we can call this dosomething function using the syntax you see here we say the namespacename, we put Colon, Colon and then we say the name of the function that we want to call.This is going to work.We can even call the add function, which is going to do something different.It’s going to minus one just to show that it is a different function.It is possible to do something like that and to call this function inside the namespace, you will needto prefix it with the namespace name.Now what do you do if you want to call the function here on the outside, you can directly call it andit is going to be called.But you can also specify colon Colon to mean that you are calling the function from the outside.And this is going to be really cool that what you see that we do here in our namespace, we’re goingto call the function from the outside, but we are going to be calling it inside our namespace.And the colon Colon here is going to remove the confusion for the compiler.Now that you see this, we’re going to head over to Visual Studio code and play with this a little more.Okay.Here we are in our working folder.The current project is Default Global namespace.We’re going to set up our template files.We’re going to copy them and we’re going to put them in the current project.We’re going to open this in Visual Studio code by dragging and dropping here and we’re going to haveour main CPP file.We’re going to clean it up a little bit and close the left sidebar here to save on time.I am going to put in the code we’re going to be using here.It is really nothing complicated.We have an Add function which is going to add up the parameters that it gets.It’s going to return A plus B and you’re going to get the sum returned here.We also have a namespace called Mything.Inside the namespace we have another function called Add, and it’s going to add up two parameters andreturn the result.But notice that it’s going to minus one just to do some adjustment to show you that you can have thefunctions named the same way inside the different namespaces.We also have a do something function which is going to call the Add function that we have on the outside.And we are using this setup here to try and remove the confusion as to how do you use these two functionshere that have the same name.Now we have our function, we have our namespace.Let’s try and use these things in the main function.Suppose we want to call the do something function here.What we can do is say my thing.We are going to say the namespace name and we’re going to say do something and this is going to callour function here.It’s going to give us the result and it’s going to print it out.Let’s go ahead and run this program to show that it is actually working.We’re going to bring up a terminal.You see that the build is good.We’re going to clear and run Rooster.You’re going to see that result is 11.Five plus six is 11.And we are calling the add function from the outside here.And this thing happens to be living in the global namespace.It doesn’t live in any namespace.Okay, so this is what I want you to see.Anything that is not explicitly living in any namespace is going to be living in the default globalnamespace.And if you want to call something from the global namespace, you use the syntax we use here.You say Colon, Colon and the compiler is going to know that you want to call something declared ordefined in the global namespace.Suppose we go in our namespace here and we remove the colon colon here.Let’s try and see what is going to happen.The compiler is going to choose the closest thing it can get its hands on and it’s going to see howI am living in my thing namespace.If somebody calls the Add function, I am going to call the add function in the namespace where I amliving.And if we build this program, we’re going to see that the build is good.But if we run it, we’re going to see that we are calling the Add function, which is going to minusone.And this may not be what you want if.If what you want is ready to call this function, then this is fine.But if you intend to call the function on the outside here, you’re not going to get what you expect.Let’s world.I think we have worked this, but let’s just world again to be sure we’re going to see that the worldis good.We can clear and run Rooster.We’re going to see that now.The result is done because the function we are calling is the function inside our namespace.Here again, if what we want is the function living in the global namespace, we have to prefix thatwith colon.Colon and the compiler is going to know.Huh.This guy is calling the function not in this namespace here and the compiler is going to search outsidethis namespace and it’s going to look into the global namespace here.And if we build and run this program, we’re going to get what we expect.We’re going to clear and run Rooster.We’re going to see that the result now is 11.This is really all I had to share in this lecture, showing you how you can set up things in the globalnamespace if you want to call stuff.Living in the global namespace, this is the syntax you use.You say Colon, Colon and the compiler is going to note that it’s going to go and look in the globalnamespace and look for the names that you are calling here and everything is going to work as you expect.We are going to stop here in this lecture.In the next one, we’re going to see that there are some already built in namespaces that we have actuallybeen using all along without knowing.And we’re going to see a little bit about that.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,同学们,准备好了吗?今天我们将深入学习 C++ 中一个非常基础但极其重要的概念——默认全局命名空间 [Default Global Namespace]。

C++ 默认全局命名空间详解:你的代码在哪里“安家”?

简要总结: 本节课将详细讲解 C++ 中默认全局命名空间的概念:它是指任何不在显式 namespace 块内的代码元素所在的“家”。我们将学习如何声明和访问这些元素,并通过 作用域解析运算符 [Scope Resolution Operator] :: 来明确地引用它们,特别是在存在同名函数或变量时,避免混淆。


1. 深入理解默认全局命名空间

在 C++ 中,为了管理大量的代码元素(如函数、变量、类等),防止它们之间因为名字相同而产生冲突(也就是 名称冲突 [Name Collision]),我们引入了 命名空间 [Namespace] 的概念。你可以把命名空间想象成一个大箱子,你可以把相关的代码放在同一个箱子里,并给这个箱子起一个名字,这样就很容易区分不同箱子里的同名物品了。

那么,什么是 默认全局命名空间 呢?

  • 它的本质: C++ 中,任何没有被显式地定义在任何 namespace 块内部的代码元素(比如一个函数、一个变量、一个类定义等),都自动地存在于一个特殊的、没有名字的“顶层”命名空间中。这个没有名字的“顶层”空间,我们称之为 默认全局命名空间
  • 它的位置: 简单来说,就是在你的 .cpp 文件中,所有 namespace {} 块外面直接定义的代码,都属于这个默认全局命名空间。
  • 例子: 在我们常见的 main 函数中,如果你直接定义一个函数或者变量,它们就位于默认全局命名空间里。例如:

    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <iostream>

    // 这是一个在默认全局命名空间中的函数
    void greet() {
    std::cout << "Hello from global namespace!" << std::endl;
    }

    int main() {
    greet(); // 在 main 函数中直接调用全局函数
    return 0;
    }

2. 如何声明和访问命名空间中的成员

为了更好地理解默认全局命名空间,我们还需要知道如何使用 具名命名空间 [Named Namespace] 及其成员。

2.1 声明命名空间成员

  • 默认全局命名空间中的成员:

    • 直接在任何 namespace 块的外部定义即可。
    • 例如:int global_variable = 10;void global_function();
  • 具名命名空间中的成员:

    • 使用 namespace YourNamespaceName { /* 成员声明或定义 */ } 语法。
    • 例如:
      cpp
      1
      2
      3
      4
      5
      namespace MyLibrary {
      int calculate(int a, int b) {
      return a * b;
      }
      }

2.2 访问命名空间成员

访问命名空间中的成员,我们需要使用 作用域解析运算符 ::

  • 访问具名命名空间中的成员:

    • 语法:命名空间名::成员名
    • 示例:MyLibrary::calculate(5, 3);
  • 访问默认全局命名空间中的成员:

    • 情况一:没有名称冲突。 如果当前作用域内或你所在的具名命名空间内没有同名的函数或变量,你可以直接通过成员名访问默认全局命名空间中的成员。
      cpp
      1
      2
      3
      4
      5
      6
      7
      8
      9
      void printGlobalMessage() {
      // ...
      }

      namespace MyNamespace {
      void doSomething() {
      printGlobalMessage(); // 直接调用全局函数,因为MyNamespace内部没有同名函数
      }
      }
    • 情况二:存在名称冲突时,明确指定访问全局。 当你当前所在的具名命名空间内部有一个与全局命名空间中同名的函数或变量时,为了明确告诉编译器你想调用的是全局的那个,你需要在成员名前加上 ::

      • 语法:::成员名
      • 这个 :: 前面没有命名空间名,就意味着它指向的是 默认全局命名空间
      • 这正是本节课最重要的概念之一! 它能够帮助你解决代码中出现的名称冲突问题,明确指定要使用的函数或变量版本。
      cpp
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      // 在默认全局命名空间中
      void Add(int a, int b) {
      std::cout << "Global Add: " << a + b << std::endl;
      }

      namespace MyMath {
      // 在 MyMath 命名空间中,有一个同名的 Add 函数
      void Add(int a, int b) {
      std::cout << "MyMath Add (minus 1): " << (a + b - 1) << std::endl;
      }

      void doCalculations(int x, int y) {
      // 如果只写 Add(x, y); 编译器会优先在 MyMath 内部查找,调用 MyMath::Add
      Add(x, y); // 输出: MyMath Add (minus 1): ...

      // 如果想调用全局的 Add 函数,必须使用 ::Add
      ::Add(x, y); // 输出: Global Add: ...
      }
      }

      int main() {
      MyMath::doCalculations(10, 5);
      return 0;
      }

3. C++ 代码示例:深入理解名称解析

下面的代码演示了默认全局命名空间、具名命名空间以及如何使用 :: 运算符来解决名称冲突和明确访问所需的函数。

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream> // 引入 iostream 库,其中 std::cout 等也在一个具名命名空间 std 中

// =======================================================
// 1. 在默认全局命名空间中定义一个 Add 函数
// 任何不在 'namespace' 块内的代码都属于这里
// =======================================================
int Add(int a, int b) {
std::cout << "DEBUG: Calling GLOBAL Add function. ";
return a + b;
}

// =======================================================
// 2. 定义一个具名命名空间 'MyThing'
// =======================================================
namespace MyThing {

// 2.1 在 MyThing 命名空间中定义一个同名的 Add 函数
// 注意:这个 Add 会做一些不同的操作 (参数和然后减去1),以示区别
int Add(int a, int b) {
std::cout << "DEBUG: Calling MyThing::Add function (with -1 adjustment). ";
return (a + b - 1); // 故意减去1,用来区分
}

// 2.2 在 MyThing 命名空间中定义一个 DoSomething 函数
// 这个函数将演示在具名命名空间内部如何调用不同的 Add 函数
void DoSomething(int val1, int val2) {
std::cout << "--- Inside MyThing::DoSomething ---" << std::endl;

// 尝试调用 Add 函数:
// 编译器首先会在当前命名空间 (MyThing) 中查找 Add。
// 如果找到,就会调用 MyThing::Add。
int result1 = Add(val1, val2);
std::cout << "Result of Add(val1, val2) within MyThing: " << result1 << std::endl;

// 明确调用全局命名空间中的 Add 函数:
// 使用 '::' 前缀告诉编译器,请从全局命名空间中查找 Add。
int result2 = ::Add(val1, val2);
std::cout << "Result of ::Add(val1, val2) (global) within MyThing: " << result2 << std::endl;

std::cout << "--- End of MyThing::DoSomething ---" << std::endl;
}

// 2.3 另一个函数,用于展示如何从外部调用
void printMessage() {
std::cout << "Hello from MyThing namespace!" << std::endl;
}

} // end of namespace MyThing

// =======================================================
// 3. main 函数:程序执行的入口点
// =======================================================
int main() {
std::cout << "=== Starting Program ===" << std::endl;

// 3.1 调用 MyThing 命名空间中的 DoSomething 函数
// 语法:命名空间名::函数名
MyThing::DoSomething(5, 6); // 预期 MyThing::Add 结果是 5+6-1=10, 全局 Add 结果是 5+6=11

std::cout << "\n--- In main function (global scope) ---" << std::endl;

// 3.2 在 main 函数(位于全局作用域)中直接调用 Add 函数
// 编译器会直接找到并调用全局命名空间中的 Add 函数
int globalSum = Add(10, 20); // 调用全局 Add
std::cout << "Result of Add(10, 20) directly in main (global): " << globalSum << std::endl;

// 3.3 在 main 函数中明确指定调用全局命名空间中的 Add 函数
// 即使在这里不加 '::' 也能调用全局 Add,但加上 '::' 更能体现其意图
int globalSumExplicit = ::Add(7, 8); // 调用全局 Add
std::cout << "Result of ::Add(7, 8) explicitly in main (global): " << globalSumExplicit << std::endl;

// 3.4 在 main 函数中调用 MyThing 命名空间中的 Add 函数
// 必须使用命名空间名::函数名
int myThingSum = MyThing::Add(10, 20); // 调用 MyThing::Add
std::cout << "Result of MyThing::Add(10, 20) in main: " << myThingSum << std::endl;

// 3.5 调用 MyThing 命名空间中的另一个函数
MyThing::printMessage();

std::cout << "=== Program Finished ===" << std::endl;
return 0;
}

代码运行预期输出:

Code
1
2
3
4
5
6
7
8
9
10
11
12
=== Starting Program ===
--- Inside MyThing::DoSomething ---
DEBUG: Calling MyThing::Add function (with -1 adjustment). Result of Add(val1, val2) within MyThing: 10
DEBUG: Calling GLOBAL Add function. Result of ::Add(val1, val2) (global) within MyThing: 11
--- End of MyThing::DoSomething ---

--- In main function (global scope) ---
DEBUG: Calling GLOBAL Add function. Result of Add(10, 20) directly in main (global): 30
DEBUG: Calling GLOBAL Add function. Result of ::Add(7, 8) explicitly in main (global): 15
DEBUG: Calling MyThing::Add function (with -1 adjustment). Result of MyThing::Add(10, 20) in main: 29
Hello from MyThing namespace!
=== Program Finished ===

4. QA 闪存卡 (Flash Cards)

问题 1: 什么是 C++ 的默认全局命名空间?
答案 1: 默认全局命名空间是 C++ 中一个特殊的、没有名字的顶层命名空间。任何没有被显式地定义在具名 namespace 块内部的代码元素(如函数、变量、类等)都自动地存在于这个空间中。

问题 2: 如何在默认全局命名空间中声明函数或变量?
答案 2: 只需在任何 namespace 块的外部直接声明或定义即可。例如:int global_var;void global_func();

问题 3: :: 运算符在访问全局命名空间成员时有什么作用?
答案 3: :: 运算符是作用域解析运算符。当它单独使用(即前面没有命名空间名)时,它表示要访问默认全局命名空间中的成员。这在当前作用域或具名命名空间中存在同名成员时,可以明确指示编译器去查找全局命名空间中的那个成员。

问题 4: 如果在命名空间 A 内部直接调用一个函数名 func(),而命名空间 A 和默认全局命名空间都有一个叫 func 的函数,会调用哪个?
答案 4: 编译器会优先在当前所在的命名空间 A 内部查找。如果找到了 A::func(),就会调用它。只有当 A 内部没有 func() 时,编译器才会向上层作用域(包括默认全局命名空间)查找。

问题 5: 如何确保在一个具名命名空间内部调用的是默认全局命名空间中的同名函数?
答案 5: 在函数名前加上 :: 运算符。例如,在 MyNamespace 内部,如果想调用全局的 myFunction(),就写 ::myFunction();

5. 常见误解与错误

  1. 误解 1::: 只能用于访问全局命名空间。
    • 真相: :: 是作用域解析运算符,其通用形式是 NamespaceName::MemberName。当 NamespaceName 被省略时,它特指全局命名空间。所以,它不仅可以用于访问全局命名空间,还可以用于访问任何具名命名空间中的成员(例如 std::cout)。
  2. 误解 2:在不同命名空间中定义同名函数会导致编译错误。
    • 真相: 不会。这是命名空间的主要目的——允许在不同命名空间中定义相同的名称而不会引起冲突。编译器会根据你如何引用它们来解析名称。
  3. 误解 3:忘记或错误使用 :: 导致调用了错误的函数。
    • 真相: 这是初学者最常见的错误。如果你在一个具名命名空间 MyNamespace 内部,想调用全局的 Add() 函数,但忘记写成 ::Add(),而 MyNamespace 内部也定义了 Add(),那么你将会意外地调用到 MyNamespace::Add(),这可能不是你期望的结果。
  4. 误解 4:过度依赖 using namespace SomeNamespace;
    • 真相: using namespace SomeNamespace; 可以将 SomeNamespace 中的所有名称引入到当前作用域,让你无需使用 SomeNamespace:: 前缀。但这可能导致新的名称冲突,特别是在引入多个命名空间或有大量全局同名符号时。在大型项目中,通常推荐显式使用 NamespaceName::MemberNameusing SomeNamespace::SpecificMember; 来避免潜在的名称冲突。

6. 编程练习

现在,你已经对默认全局命名空间和 :: 运算符有了基本的了解。是时候动手实践了!

练习目标:
请补全下面的 C++ 代码。你的任务是:

  1. MyUtility 命名空间内部的 performCalculation() 函数中,调用 默认全局命名空间 中的 multiply() 函数。
  2. main() 函数中,调用 MyUtility 命名空间中的 printWelcome() 函数。
  3. main() 函数中,直接调用 默认全局命名空间 中的 printWelcome() 函数。

代码骨架:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>

// 1. 在默认全局命名空间中定义一个函数
void printWelcome() {
std::cout << "Welcome from the GLOBAL space!" << std::endl;
}

// 2. 在默认全局命名空间中定义另一个函数
int multiply(int a, int b) {
std::cout << " (DEBUG: GLOBAL multiply called) ";
return a * b;
}

// 3. 定义一个具名命名空间
namespace MyUtility {

// 3.1 在 MyUtility 命名空间中定义一个同名的 printWelcome 函数
void printWelcome() {
std::cout << "Welcome from MyUtility namespace!" << std::endl;
}

// 3.2 在 MyUtility 命名空间中定义一个 performCalculation 函数
void performCalculation(int x, int y) {
std::cout << "Inside MyUtility::performCalculation:" << std::endl;
// TODO: 在这里调用默认全局命名空间中的 multiply 函数
// 并打印结果,例如:std::cout << "Result of global multiply: " << result << std::endl;

// 请在这里添加你的代码
int result = /* 你的代码 */ (x, y);
std::cout << "Result of global multiply: " << result << std::endl;
}

} // end of namespace MyUtility

int main() {
std::cout << "--- Main Function ---" << std::endl;

// TODO: 调用 MyUtility 命名空间中的 printWelcome 函数

// 请在这里添加你的代码
/* 你的代码 */ ;

// TODO: 调用 MyUtility 命名空间中的 performCalculation 函数,传入任意两个整数
// 例如:MyUtility::performCalculation(5, 7);

// 请在这里添加你的代码
/* 你的代码 */ ;

// TODO: 调用默认全局命名空间中的 printWelcome 函数

// 请在这里添加你的代码
/* 你的代码 */ ;

std::cout << "--- End Main Function ---" << std::endl;
return 0;
}

请完成 TODO 部分的代码,然后告诉我你的解决方案!


评论